package com.cysoft.starter.repeat.core.aspect;

import com.cysoft.starter.repeat.core.annotation.Repeat;
import com.cysoft.starter.repeat.core.helper.RepeatHelper;
import com.cysoft.starter.repeat.core.helper.RequestParamsHelper;
import com.cysoft.starter.repeat.core.helper.TokenHelper;
import com.cysoft.starter.repeat.core.model.RepeatInfo;
import com.cysoft.starter.repeat.core.properties.RepeatSubmitProperties;
import com.cysoft.starter.repeat.core.validata.RepeatValidata;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author 玖零
 * @version V0.0.1
 * @project cysoft-spring-boot-starter-repeat
 * @package com.cysoft.starter.repeat.core.aspect
 * @date 2025-03-02 16:34
 * @desc
 */
@Slf4j
public class BaseAspect {

    /**
     * 重复验证 通过构造函数注入，初始化后不可变
     */
    protected final RepeatValidata repeatValidata;

    /**
     * 重复验证配置对象，包含日志记录的各种配置信息，通过构造函数注入，初始化后不可变
     */
    protected final RepeatSubmitProperties repeatSubmitProperties;

    /**
     * 需要被验证重复提交的地址集合
     */
    protected  Set<String> repeatUrlList;


    /**
     * 防重复提交切面构造函数
     *
     * @param repeatValidata            防止重复提交验证方式
     * @param repeatSubmitProperties    防止重复提交配置文件
     */
    public BaseAspect(RepeatValidata repeatValidata, RepeatSubmitProperties repeatSubmitProperties) {
        this.repeatValidata = repeatValidata;
        this.repeatSubmitProperties = repeatSubmitProperties;
        initRepeatUrl(repeatSubmitProperties);
        log.info("BaseAspect 初始化完成，使用的验证方式: {}, 配置文件: {}", repeatValidata.getClass().getName(), repeatSubmitProperties.getClass().getName());
    }

    /**
     * 初始化需要被重复验证的 URL
     *
     * @param repeatSubmitProperties 需要验证的 URL 列表
     */
    private void initRepeatUrl(RepeatSubmitProperties repeatSubmitProperties) {
        log.info("开始初始化需要被重复验证的 URL");
        RepeatHelper.initRepeat(repeatUrlList, repeatSubmitProperties.getUrls(), set -> repeatUrlList = set);
        log.info("需要被重复验证的 URL 初始化完成，URL 数量: {}", repeatUrlList.size());
    }

    /**
     * 检查是否需要进行重复提交验证
     *
     * @param request HttpServletRequest 对象
     * @param point   ProceedingJoinPoint 对象
     * @return 是否需要进行重复提交验证
     */
    protected Boolean checkRepeat(HttpServletRequest request, ProceedingJoinPoint point) {
        // 输入参数空值检查
        if (Objects.isNull(request) || Objects.isNull(point)) {
            log.warn("输入参数请求 或 ProceedingJoinPoint 为空，不进行重复提交验证");
            return false;
        }
        // 获取请求 URI
        String requestURI = request.getRequestURI();
        // 检查方法或者类上是否存在 @Repeat 注解
        boolean hasRepeatAnnotation = hasRepeatAnnotation(point);
        // 合并条件判断
        boolean shouldRepeat = repeatUrlList.contains(requestURI) || hasRepeatAnnotation;
        // 记录日志
        log.info("Request with URI {} is {} repeat validata.", requestURI, shouldRepeat ? "" : "not ");
        return shouldRepeat;
    }

    /**
     * 判断是否包含重复验证注解
     *
     * @param point ProceedingJoinPoint 对象
     * @return 是否包含重复验证注解
     */
    private boolean hasRepeatAnnotation(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        // 检查方法上是否存在 @Repeat 注解
        if (method.isAnnotationPresent(Repeat.class)) {
            log.info("方法 {} 上存在 @Repeat 注解", method.getName());
            return true;
        }
        // 检查类上是否存在 @Repeat 注解
        Class<?> targetClass = point.getTarget().getClass();
        if (targetClass.isAnnotationPresent(Repeat.class)) {
            log.info("类 {} 上存在 @Repeat 注解", targetClass.getName());
            return true;
        }
        return false;
    }


    /**
     * 生成唯一请求的 key
     *
     * @param request HttpServletRequest 对象
     * @param point   ProceedingJoinPoint 对象
     * @return 生成的唯一请求 key
     */
    protected String generateRequestKey(HttpServletRequest request, ProceedingJoinPoint point) {
        StringBuilder keyBuilder = new StringBuilder();
        try {
            // 获取请求的 URI 和请求方法
            String uri = request.getRequestURI();
            String method = request.getMethod();
            keyBuilder.append(method).append(":").append(uri);

            // 获取唯一标识
            String uniqueIdentification = TokenHelper.getTokenFromRequest(request, repeatSubmitProperties.getUniqueIdentification());
            if (Objects.nonNull(uniqueIdentification)) {
                keyBuilder.append(":").append(uniqueIdentification);
            }

            // 获取请求参数
            String requestParams = RequestParamsHelper.getRequestParams(point);
            if (!requestParams.isEmpty()) {
                keyBuilder.append(":").append(requestParams);
            }
        } catch (Exception e) {
            log.error("生成请求 key 时发生异常，请求 URI: {}", request.getRequestURI(), e);
            return null;
        }
        String requestKey = keyBuilder.toString();
        log.info("生成的请求 key: {}", requestKey);
        return requestKey;
    }




    /**
     * 获取超时时间
     *
     * @param request HttpServletRequest 对象
     * @param point   ProceedingJoinPoint 对象
     * @return 超时时间
     */
    protected long getExpireTime(HttpServletRequest request, ProceedingJoinPoint point) {
        long expireTime = 0;
        try {
            // 获取方法签名和方法对象
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();

            // 优先检查方法上是否存在 @Repeat 注解
            Repeat methodAnnotation = method.getAnnotation(Repeat.class);
            if (methodAnnotation != null) {
                expireTime = methodAnnotation.expireTime();
                log.debug("从方法 [{}] 上的 @Repeat 注解获取到超时时间: {}", method.getName(), expireTime);
                return expireTime;
            }

            // 若方法上没有，检查类上是否存在 @Repeat 注解
            Class<?> targetClass = point.getTarget().getClass();
            Repeat classAnnotation = targetClass.getAnnotation(Repeat.class);
            if (classAnnotation != null) {
                expireTime = classAnnotation.expireTime();
                log.debug("从类 [{}] 上的 @Repeat 注解获取到超时时间: {}", targetClass.getName(), expireTime);
                return expireTime;
            }

            // 若方法和类上都没有，使用配置文件中的超时时间
            expireTime = repeatSubmitProperties.getExpireTime();
            log.debug("从配置文件中获取到超时时间: {}", expireTime);
        } catch (Exception e) {
            log.error("获取超时时间时发生异常，请求 URI: {}", request.getRequestURI(), e);
            // 发生异常时使用配置文件中的默认值
            expireTime = repeatSubmitProperties.getExpireTime();
        }
        return expireTime;
    }


    /**
     * 获取超时时间的时间单位
     * 优先从方法上的 @Repeat 注解获取，若方法上没有则从类上的 @Repeat 注解获取，
     * 若类上也没有则从配置文件中获取。
     *
     * @param request HttpServletRequest 对象，代表当前的 HTTP 请求
     * @param point   ProceedingJoinPoint 对象，用于获取目标方法和类的信息
     * @return 超时时间的时间单位，如果发生异常则返回配置文件中的默认时间单位
     */
    protected TimeUnit getExpireTimeUnit(HttpServletRequest request, ProceedingJoinPoint point) {
        TimeUnit timeUnit = null;
        try {
            // 获取方法签名和方法对象
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();

            // 优先检查方法上是否存在 @Repeat 注解
            Repeat methodAnnotation = method.getAnnotation(Repeat.class);
            if (methodAnnotation != null) {
                timeUnit = methodAnnotation.timeUnit();
                log.debug("从方法 [{}] 上的 @Repeat 注解获取到时间单位: {}", method.getName(), timeUnit);
                return timeUnit;
            }

            // 若方法上没有，检查类上是否存在 @Repeat 注解
            Class<?> targetClass = point.getTarget().getClass();
            Repeat classAnnotation = targetClass.getAnnotation(Repeat.class);
            if (classAnnotation != null) {
                // 修正此处，应该使用 classAnnotation 来获取时间单位
                timeUnit = classAnnotation.timeUnit();
                log.debug("从类 [{}] 上的 @Repeat 注解获取到时间单位: {}", targetClass.getName(), timeUnit);
                return timeUnit;
            }

            // 若方法和类上都没有，使用配置文件中的时间单位
            timeUnit = repeatSubmitProperties.getTimeUnit();
            log.debug("从配置文件中获取到时间单位: {}", timeUnit);
        } catch (Exception e) {
            log.error("获取时间单位时发生异常，请求 URI: {}", request.getRequestURI(), e);
            // 发生异常时使用配置文件中的默认值
            timeUnit = repeatSubmitProperties.getTimeUnit();
        }
        return timeUnit;
    }

    /**
     * 组装重复验证参数
     *
     * @param request HttpServletRequest 对象
     * @param point   ProceedingJoinPoint 对象
     * @return 重复验证参数对象
     */
    protected RepeatInfo assemblyRepeat(HttpServletRequest request, ProceedingJoinPoint point) {
        RepeatInfo repeatInfo = new RepeatInfo();
        repeatInfo.setRequestKey(this.generateRequestKey(request, point));
        repeatInfo.setExpireTime(this.getExpireTime(request, point));
        repeatInfo.setExpireTimeUnit(this.getExpireTimeUnit(request, point));
        log.info("组装的重复验证参数: {}", repeatInfo);
        return repeatInfo;
    }
}
